/******************************************************************************
 * \brief	演示延时与函数，控制LCD灯闪烁的间隔
 * \details	使用Keil Simulator模拟器，和Debug时的Logic Analyzer示波器窗口查看输出
 * \remark	File format: UTF-8，源文件使用UTF-8中文编码
 * \note	省略了8051的初始化，使用模拟器时会自动准备好运行环境
 * \author	将狼才鲸
 * \date	2024-07-23
 ******************************************************************************/

/* 一个文件中将一些全局的东西分门别类，模块性更好，不容易错漏，方便查找bug */

/********************************* 头文件 *************************************/
#include <Atmel/at89c51RC2.h>	/* 寄存器定义 */

/******************************** 类型定义 ************************************/
/** 下面的类型定义让程序的可移植性更好，例如无负担的移植到32位或64位MCU上；
    typedef是C语言进行类型定义的关键字，很常用 */
typedef unsigned char		uint8;		/* 使用uint8代替8051一个字节的unsigned char */
typedef unsigned int		uint16;		/* 使用uint16代替8051两个字节的unsigned int，8051的int是2字节，而32位CPU是4字节 */
typedef unsigned long int	uint32;		/* 8051的long是4字节，而32位CPU是8字节，4字节的float和8字节的double与32位CPU一致 */
typedef char				int8;
typedef int					int16;
typedef long int			int32;

/********************************* 宏定义 *************************************/
/** 定义错误码，正式工程建议所有的返回值都用错误码，不要直接返回 -1，这样模块化更好 */
#define	OK			0
#define ERR			(-1)	/* 宏定义如果是表达式，建议用括号括起来，防止优先级错误导致难定位的bug出现 */
#define ERR_PARAM	(-2)	/* 传入的参数错误 */

/****************************** 结构体定义*************************************/
typedef enum _BOOL {	/* enum是C语言的关键词，联合体，很常用 */
	FALSE = 0,
	TRUE		/* enum的项目如果是缺省值，则值默认是上一个值 + 1 */
} BOOL;	/* C语言标准库没有定义布尔类型，所以自己定义，当然，Linux和Windows的一些库文件中会有 */

/******************************* 函数声明 *************************************/
static int16 sleep_us_10x(int16 cnt);	/* 函数声明，如果有函数在定义前就被调用，则需要在调用前进行声明 */
static int16 sleep_ms(int16 cnt);	/* static是C语言关键字，表示静态变量或静态函数，这里是静态函数 */

/******************************* 接口函数 *************************************/
/**
 * \brief	主函数
 * \details	当整个工程文件中没有汇编文件时，Debug后默认进入到main函数第一行，
 *			但当有汇编文件时，默认会先进入到汇编，此时则需要写好boot
 */
int main(void)
{
	/* 进入函数时先拉低拉高做标记，作为延时的起始零点，因为main执行前会有耗时 */
	P1_0 = 0;	/* IO口输出低；P1_0是头文件中已经定义的寄存器Bit */
	P1_0 = 1;	/* IO口输出高 */
	
	while (1)
	{
		/**
		 请在Debug后通过 View--Analysis Windows--Logic Analyzer--Setup--点击右上角叉叉左边的新建图标--键入P1_0--Close 来创建模拟器中示波器的采样引脚；
		 把示波器的小窗口向下拉一点，露出波形的显示区域；
		 再点击Zoom中的All来显示整个波形（不点击会看不到变化的波形）；
		 再F10单步运行，一边运行一边看输出的矩形波。
		 */
		P1_0 = 0;			/* IO口输出低；P1_0是头文件中已经定义的寄存器Bit */
		sleep_us_10x(500);	/* 延时5ms */
		P1_0 = 1;			/* IO口输出高 */
		sleep_ms(15);		/* 延时15ms */
	}
}

/******************************* 私有函数 *************************************/
/**
 * \brief	us级别延时，延时时间 = cnt x 10 us
 * \details	没使用定时器，只使用了死循环，当前延时只是大体上准，而且会受中断的影响；
 *			只延时10us时，误差+13.5us；
 *			延时20us及以上时，误差 + 0.25% x 想要的延时 + 13.5us，例如想要延时1ms，实际会延时1016us；
 * \param	cnt	延时多少个10us，有效取值范围为 1 ~ 32768
 * \return	返回值无实际含义
 */
static int16 sleep_us_10x(int16 cnt)	/* 不是供其它模块调用的接口函数，都建议定义成静态函数，模块化更好 */
{
	int16 i;	/* 8051的所有局部变量必须在函数的前面定义，不能像32位CPU那样随用随定义 */
	int8 ret = 0;

	/* cnt为0时延时12us，cnt为1时延时19.5us，通过实测得到 */
	/* 循环1001次耗时8021us，每次循环消耗8.0015us，通过实测得到 */

	for (i = 0; i < cnt; i++)	/* 8051不能使用 for (int i = 0; i < cnt; i++); 这种使用时定义的做法 */
	{
		/** 每次空循环耗时8us，此处再补2us，补成10us */
		/* 也可以使用 __asm NOP; 插入汇编，但是这样会导致.c文件打不了断点，所以不用 */
		/* 指令集参见：https://gitee.com/langcai1943/8051-from-boot-to-application/blob/develop/02_doc/01_8051寄存器、指令集、伪指令和关键字介绍.md */
		ret++;	/* 字节自增在汇编中消耗1个机器周期，当前24MHz晶振，一个机器周期0.5us */
		ret++;
		ret++;
		ret++;
	}

	return ret;	/* 使用ret，防止此变量的所有操作被编译器优化掉；该返回值没有含义 */
}

/**
 * \brief	ms级别延时
 * \details	没使用定时器，只使用了死循环，当前延时只是大体上准，而且会受中断的影响；
 *			延时误差约 + 0.1%，例如延时1000ms，实际耗时约1001ms；
 * \param	cnt	延时多少个ms，有效取值范围为 1 ~ 32768
 * \return	返回值无实际含义
 */
static int16 sleep_ms(int16 cnt)
{
	int16 i, j;
#	define MS_SLEEP_CNT	356	/* 延时1ms所需要的循环次数，实测所得 */

	for (i = 0; i < cnt; i++)
	{
		for (j = 0; j < MS_SLEEP_CNT; j++)
		{
		}
	}

	return 0;	/* 返回0代表一切执行正常，处理错误则返回负数的错误码；建议所有函数都弄成有返回值的结构，中断处理除外 */
}

/********************************* 文件尾 *************************************/
